
	/***********************************************************************************************

		@API
		Module: 	Storage
		Require:	Data

		Api:
			st	load					(size*)
			st 	save					(id, v, ttl*)
			v		read					(id)
			b		exists				(id)
			b		remove				(id)
			st	clear					()
			n		size					()
			md	metadata			(id*)

		Infos:
			memory { size, used, free, data, metadata }

		Events:
			beforeLoad, beforeSave, beforeUpdate, beforeInsert, beforeRead, beforeRemove,	beforeClear	, beforeOverflow
			load			, save			, bupdate			, insert			, read			, remove			, clear				,	overflow

		Legend:
			ss[] 	{ id, size, toc, tou, ttl }
			toc		time of creation
			tou		time of update
			ttl		time to live

	************************************************************************************************/

	//-------------------------------------------------------------------------------
	jsl.Storage = (function(){
		try {
			var mod 		= new jsl.Module("jsl.Storage")
			mod.__load 	= false
			mod.__uz		= false
			mod.__table	= null
			mod.__size	= null
			mod.__map		= {}
			mod.__dsize	= 0

			//Events
			//-------------------------------------------------------------------------------
			mod.events("beforeLoad"	, "beforeSave",	"beforeUpdate", "beforeInsert", "beforeRead", "beforeRemove", "beforeClear"	, "beforeOverflow")
			mod.events("load"				, "save"			,	"update"			, "insert"			, "read"			, "remove"			,	"clear"				, "overflow")

			mod.eventAction("overflow", function(e) {
				throw jsl.OverflowError("storage.Overflow")
			})

			//Infos
			//-------------------------------------------------------------------------------
			mod.info("memory", {})
			mod.info("memory.size"			, function() { this.__update(); return this.__table.sz 																						})
			mod.info("memory.used"			, function() { return this.size()																								 									})
			mod.info("memory.free"			, function() { return -this.size() + this.__table.sz		 																					})
			mod.info("memory.metadata"	, function() { this.__update(); return this.__dsize ? jsl.Data.serialize(this.__table).length	: 0	})
			mod.info("memory.data"			, function() { this.__update(); return this.__dsize			 																					})

			return mod
		}
		catch(e) {
			jsl.throwError(e, "jsl.Storage")
		}
	})()
	//-------------------------------------------------------------------------------
	jsl.Storage.load = function(size) {
		try {//!
			if(arguments.length > 1) 	throw jsl.ParamError()
			if(arguments.length)			jsl.validate(size, "i++", "size")
			if(this.__load)						throw jsl.MisuseError()//!

			this.__size = +size
			this.__update()

			return this
		}
		catch(e) {
			jsl.throwError(e, this, arguments)
		}
	}
	//-------------------------------------------------------------------------------
	jsl.Storage.save = function(id, v, ttl) {
		try {
			var e, t, td, s, b, u, vid
			//!
			if(arguments.length < 2 || arguments.length > 3)	throw jsl.ParamError()
			jsl.validate(id, "s+", "id")
			if(arguments.length === 3)	jsl.validate(ttl, "i+", "ttl")//!

			this.__update()

			vid	= id + ""

			if(vid.charAt(0) === "!") s	= jsl.Data.serialize(v, { compress: true }), vid = vid.slice(1)
			else											s	= jsl.Data.serialize(v)

			e 	= { id: vid, value: v, ttl: +ttl || 0, txt: s }
			this.throwEvent("beforeSave", e)
			t 	= this.__table
			td	=	u = this.__map[vid]

			if(u)	this.throwEvent("beforeUpdate", e)
			else	this.throwEvent("beforeInsert", e)

			if(this.size() - (td && td.size || 0) + s.length > this.__table.sz) {
				this.throwEvent("beforeOverflow", e)
				this.throwEvent("overflow", e)
			}
			else {

				if(u) {
					td.tou	= +new Date
					td.ttl	= arguments.length === 3 ? +ttl : td.ttl
				}
				else {
					td			= {}
					td.id		= vid
					td.toc	= +new Date
					td.tou	= td.toc
					td.ttl	= +ttl || 0
					t.push(td)
				}

				this.__dsize 	 += s.length - (td.size || 0)
				td.size					= s.length
				this.__map[vid]	= td

				b = this.__ni_save(vid, s, ttl)

				//the real supported size can be less than that declared
				if(!b) {
					this.throwEvent("beforeOverflow", e)
					this.throwEvent("overflow", e)
				}
				else {
					if(u)	this.throwEvent("update", e)
					else	this.throwEvent("insert", e)

					this.throwEvent("save", e)
				}
			}
			return this
		}
		catch(e) {
			jsl.throwError(e, this, arguments)
		}
	}
	//-------------------------------------------------------------------------------
	jsl.Storage.read = function(id) {
		try {
			var e, v//!
			if(arguments.length !== 1)	throw jsl.ParamError()
			jsl.validate(id, "s+", "id")//!

			this.__update()
			e = { id: id + "", exists: this.__map.hasOwnProperty(id) }
			this.throwEvent("beforeRead", e)
			v = this.__map.hasOwnProperty(id) ? jsl.Data.deserialize(this.__ni_read(id)) : undefined

			e.value = v
			this.throwEvent("read", e)
			return v
		}
		catch(e) {
			jsl.throwError(e, this, arguments)
		}
	}
	//-------------------------------------------------------------------------------
	jsl.Storage.exists = function(id) {
		try {//!
			if(arguments.length !== 1)	throw jsl.ParamError()
			jsl.validate(id, "s+", "id")//!

			this.__update()
			return this.__map.hasOwnProperty(id)
		}
		catch(e) {
			jsl.throwError(e, this, arguments)
		}
	}
	//-------------------------------------------------------------------------------
	jsl.Storage.remove = function(id) {
		try {
			var t, e, m, b//!
			if(arguments.length !== 1)	throw jsl.ParamError()
			jsl.validate(id, "s+", "id")//!

			if(!this.__uz) this.__update()
			m = this.__map
			b = m[id]

			if(b) {

				e = { id: id + "" }
				this.throwEvent("beforeRemove", e)
				t = this.__table

				this.__dsize -= m[id].size
				delete m[id]

				for(var a = 0, l = t.length; a < l; a++)
					if(t[a].id == id) {
						t.splice(a, 1)
						break
					}

				this.__ni_remove(id)
				this.throwEvent("remove", e)
			}

			return !!b
		}
		catch(e) {
			jsl.throwError(e, this, arguments)
		}
	}
	//-------------------------------------------------------------------------------
	jsl.Storage.clear = function() {
		try {
			var e, t//!
			if(arguments.length)	throw jsl.ParamError()//!

			this.__update()

			e = {}
			this.throwEvent("beforeClear", e)

			t								= this.__table
			this.__table		= []
			this.__table.sz	= t.sz
			this.__map			= {}
			this.__dsize 		= 0
			this.__ni_clear()

			this.throwEvent("clear", e)
			return this
		}
		catch(e) {
			jsl.throwError(e, this, arguments)
		}
	}
	//-------------------------------------------------------------------------------
	jsl.Storage.size = function() {
		try {//!
			if(arguments.length)	throw jsl.ParamError()//!
			this.__update()
			return this.__dsize ? this.__dsize + jsl.Data.serialize(this.__table).length : 0
		}
		catch(e) {
			jsl.throwError(e, this, arguments)
		}
	}
	//-------------------------------------------------------------------------------
	jsl.Storage.metadata = function(id) {
		try {
			var rv, t, ta//!
			if(arguments.length > 1)								throw jsl.ParamError()
			if(arguments.length)										jsl.validate(id, "s+", "id")
			if(arguments.length && !this.__map[id])	throw jsl.ParamError("NotDefined", [ id ])//!

			this.__update()
			t	= this.__table

			if(id) {
				ta = this.__map[id]
				rv = {
					id: 	ta.id,
					size:	ta.size,
					toc:	ta.toc,
					tou:	ta.tou,
					ttl:	ta.ttl
				}
			}
			else {
				rv 			= []
				rv.size = this.__dsize

				for(var a = 0, l = t.length; a < l; a++) {
					ta		= t[a]
					rv[a] = {
						id: 	ta.id,
						size:	ta.size,
						toc:	ta.toc,
						tou:	ta.tou,
						ttl:	ta.ttl
					}
				}
			}
			return rv
		}
		catch(e) {
			jsl.throwError(e, this, arguments)
		}
	}
	//Private
	//-------------------------------------------------------------------------------
	jsl.Storage.__update = function() {
		try {
			var t, a, l, e, d

			this.__uz = true

			if(!this.__load) {

				jsl.require("data")

				e = {}
				this.throwEvent("beforeLoad", e)
				this.__table	= t = this.__ni_load()
				this.__load 	= true
				l 						= t.length

				for(a = 0; a < l; a++)
					this.__dsize 				+= t[a].size,
					this.__map[t[a].id]  = t[a]

				this.throwEvent("load", e)
			}

			t = this.__table
			d = +new Date

			for(a = 0; a < t.length; a++)
				if(t[a].ttl && t[a].toc + t[a].ttl * 1000 <= d)
					this.remove(t[a--].id)

			this.__uz = false
		}
		catch(e) {
			jsl.throwError(e, this, arguments)
		}
	}
